package codingDudes.display;

import processing.core.PApplet;
import codingDudes.geom.Point2D;
import codingDudes.utils.MathUtils;

/**
 * The DisplayObject class provides flash-like, object oriented handling of graphics.
 * 
 * Before use the DisplayObject has to be initiated using DisplayObject.initialize(PApplet).
 * All DisplayObject instances use the defined PApplet class for drawing from then on.
 * 
 * Extend this class and overwrite the draw() method to create custom graphics.
 * Use the render() method to draw the content according to the defined transformation variables.
 * The render() method will be called automatically if the DisplayObject instance is added to the display list
 * of a DisplayObjectContainer. 
 * 
 * @author Tobias Friese
 */

public class DisplayObject
{
	/* statics */
	
	/** A static reference to the initialized PApplet class. Use this for drawing. */
	protected static PApplet P5;
	
	/** Declares whether the DisplayObject class has been initialized or not. */
	protected static boolean initialized			= false;
	
	/* variables */
	protected float x								= 0;
	protected float y								= 0;
	
	protected float rotation						= 0;
	
	protected float scaleX							= 1;
	protected float scaleY							= 1;
	
	protected float alpha							= 1;
	
	protected boolean visible						= true;
	
	/** A reference to the DisplayObjectContainer this DisplayObject is a child of. False if not nested. */
	protected DisplayObjectContainer parent			= null;
	
	/** Declares whether this DisplayObject instance has been finalized or not. */
	protected boolean finalized						= false;
	
	/**
	 * Initializes the DisplayObject class for use by defining a PApplet instance
	 * to draw in.
	 * 
	 * The defined PApplet instance is available by calling the static
	 * variable P5 in every DisplayObject instance.
	 * 
	 * @param pApplet A PApplet instance for use as drawing target.
	 */
	public static void initialize(PApplet pApplet)
	{
		if(pApplet != null)
		{
			P5 = pApplet;
			initialized = true;
		}
	}
	
	/**
	 * Starts rendering process and checks for initialization and visibility first.
	 * Use this method to render the content of your DisplayObject if it is not child of any DisplyObjectContainer.
	 */
	public void render()
	{
		if(this.visible)
		{
			if(initialized)
			{
				if(!this.finalized) this.draw();
			} else throw new Error("The DisplayObject class has not yet been initialized. Call DisplayObject.initialize(pApplett) before using DisplayObjects.");
		}
	}
	
	/**
	 * This method is called automatically for drawing.
	 * 
	 * Extend this method in your subclasses and define your drawing algorithms there
	 * using the static PApplet variable P5;
	 * 
	 * You might use the pushMatrix() and popMatrix() methods to transform the
	 * drawing space according to the transformation properties of the DisplayObject instance.
	 * 
	 * Other wise use the transformations properties while drawing.
	 * Please invoke the alpha property as well.
	 */
	protected void draw()
	{
		
	}
	
	/**
	 * Uses PApplets pushMatrix method and transformation methods to
	 * transform the drawing space according to locally
	 * defined position, scale and rotation;
	 */
	protected final void pushMatrix()
	{
		P5.pushMatrix();
		
		P5.translate(this.x, this.y);
		P5.rotate(this.rotation);
		P5.scale(this.scaleX, this.scaleY);
	}
	
	/**
	 * Uses PApplets popMatrix method to end matrix transformation.
	 */
	protected final void popMatrix()
	{
		P5.popMatrix();
	}
	
	/* getter setter */
	/** Returns the x position of the DisplayObject */
	public float getX()
	{
		return this.x;
	}
	
	/** Defines the x position of the DisplayObject */
	public void setX(float value)
	{
		this.x = value;
	}
	
	/** Returns the y position of the DisplayObject */
	public float getY()
	{
		return this.y;
	}
	
	/** Defines the y position of the DisplayObject */
	public void setY(float value)
	{
		this.y = value;
	}
	
	/** Returns the position of the DisplayObject as Point2D */
	public Point2D getPosition()
	{
		return new Point2D(this.x, this.y);
	}
	
	/** Sets the position of this DispleyObject to x and y */
	public void moveTo(float x, float y)
	{
		this.setX(x);
		this.setY(y);
	}
	
	/** Changes the position of this DispleyObject by x and y */
	public void moveBy(float x, float y)
	{
		this.setX(this.x + x);
		this.setY(this.y + y);
	}
	
	/** Returns the rotation of the DisplayObject */
	public float getRotation()
	{
		return this.rotation;
	}
	
	/** Defines the rotation of the DisplayObject */
	public void setRotation(float value)
	{
		this.rotation = value;
	}
	
	/** Returns the x scale of the DisplayObject */
	public float getScaleX()
	{
		return this.scaleX;
	}
	
	/** Returns the y scale of the DisplayObject */
	public float getScaleY()
	{
		return this.scaleY;
	}
	
	/** Defines the x scale of the DisplayObject */
	public void setScaleX(float value)
	{
		this.scaleX = value;
	}
	
	/** Defines the y scale of the DisplayObject */
	public void setScaleY(float value)
	{
		this.scaleY = value;
	}
	
	/** Defines the scale of the DisplayObject */
	public void setSscale(float scaleX, float scaleY)
	{
		this.setScaleX(scaleX);
		this.setScaleY(scaleY);
	}
	
	/** Returns the alpha value (0 to 1) of the DisplayObject */
	public float getAlpha()
	{
		return this.alpha;
	}
	
	/** Defines the alpha value (0 to 1) of the DisplayObject */
	public void setAlpha(float value)
	{
		this.alpha = (float)MathUtils.constrain(value, 0, 1);
	}
	
	/** Returns the visibility value of the DisplayObject */
	public boolean getVisibility()
	{
		return this.visible;
	}
	
	/** Returns the visibility of the DisplayObject */
	public void setVisibility(boolean value)
	{
		this.visible = value;
	}
	
	/** 
	 * Returns the DOContainer that contains this DisplayObject if it is a child.
	 * If this DisplayObject is not child of any DOContainer this method returns null.
	 */
	public DisplayObjectContainer getParent()
	{
		return this.parent;
	}
	
	void setParent(DisplayObjectContainer parent)
	{
		this.parent = parent;
	}
	
	/**
	 * Returns the global position of this DisplayObject as Point2D.
	 * 
	 * @return the global position as Point2D
	 */
	public Point2D getGlobalPosition()
	{
		Point2D pos = new Point2D(this.x, this.y);
		
		if(this.parent != null)
		{
			DisplayObjectContainer container = this.parent;
			while(container != null)
			{
				pos = pos.add(container.getPosition());
				container = container.getParent();
			}
		}
		
		return pos;
	}
	
	/* finalization */
	
	/**
	 * Finalizes the DisplayObject instance. Call this if the instance is not
	 * needed anymore.
	 * 
	 * Will be called automatically before garbage collection.
	 */
	public void finalize()
	{
		// unparent
		if(this.parent != null) this.parent.removeChild(this);
		
		// set var
		this.finalized = true;
	}
}
